/*
 * Copyright (C) 2013, Apexes Network Technology. All rights reserved.
 * 
 *        http://www.apexes.net
 * 
 */
package com.cooltool.component;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.util.EventObject;
import java.util.HashMap;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.RowSorter;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

/**
 *
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 */
public class JTableEx extends JComponent {

    private static final long serialVersionUID = 1L;

    private int fixedCol = 0;
    private boolean showChecked;
    private boolean editable = true;

    private TableModel model;
    private TableModelAdapter modelAdapter;
    private CheckedModel checkedModel;

    private JTable table;
    private JTable fixedTable;
    private JTable checkedTable;

    private JViewport viewport;
    
    public JTableEx() {
        this(null);
    }

    public JTableEx(TableModel model) {
        if (model == null) {
            model = new DefaultTableModel();
        }
        checkedTable = new JTable() {
            @Override
            public boolean editCellAt(int row, int column, EventObject e) {
                boolean b = super.editCellAt(row, column, e);
                if (b) {
                    if (table.getCellEditor() != null) {
                        table.getCellEditor().stopCellEditing();
                    }
                    if (fixedTable.getCellEditor() != null) {
                        fixedTable.getCellEditor().stopCellEditing();
                    }
                }
                return b;
            }
        };
        fixedTable = new JTable() {
            private static final long serialVersionUID = 1L;
            @Override
            public boolean editCellAt(int row, int column, EventObject e) {
                boolean b = super.editCellAt(row, column, e);
                if (b) {
                    if (table.getCellEditor() != null) {
                        table.getCellEditor().stopCellEditing();
                    }
                    if (checkedTable.getCellEditor() != null) {
                        checkedTable.getCellEditor().stopCellEditing();
                    }
                }
                return b;
            }
        };
        table = new JTable() {
            private static final long serialVersionUID = 1L;
            @Override
            public boolean editCellAt(int row, int column, EventObject e) {
                boolean b = super.editCellAt(row, column, e);
                if (b) {
                    if (checkedTable.getCellEditor() != null) {
                        checkedTable.getCellEditor().stopCellEditing();
                    }
                    if (fixedTable.getCellEditor() != null) {
                        fixedTable.getCellEditor().stopCellEditing();
                    }
                }
                return b;
            }
        };
        fixedTable.setOpaque(false);
        
        ListSelectionModel listSelectionModel = new DefaultListSelectionModel();
        checkedTable.setSelectionModel(listSelectionModel);
        fixedTable.setSelectionModel(listSelectionModel);
        table.setSelectionModel(listSelectionModel);
        
        fixedTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        
        checkedTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        fixedTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        
        checkedTable.getTableHeader().setResizingAllowed(false);
        fixedTable.getTableHeader().setResizingAllowed(false);
        checkedTable.getTableHeader().setEnabled(false);
                
        JPanel panel = new JPanel(new BorderLayout());
        panel.setOpaque(false);
        panel.setBorder(null);
        panel.add(fixedTable, BorderLayout.CENTER);
        panel.add(checkedTable, BorderLayout.WEST);
        
        JPanel corner = new JPanel(new BorderLayout());
        corner.setOpaque(false);
        corner.setBorder(null);
        corner.add(fixedTable.getTableHeader(), BorderLayout.CENTER);
        corner.add(checkedTable.getTableHeader(), BorderLayout.WEST);

        JScrollPane scrollPane = new JScrollPane(table);
        scrollPane.setOpaque(false);
        scrollPane.setBorder(null);
        
        viewport = new JViewport();
        viewport.setOpaque(false);
        viewport.setView(panel);
        viewport.setPreferredSize(panel.getPreferredSize());
        scrollPane.setRowHeaderView(viewport);
        scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, corner);

        setLayout(new BorderLayout());
        add(scrollPane, BorderLayout.CENTER);

        setModel(model);
    }
    
    private TableModelListener tableModelListener = new TableModelListener() {
        @Override
        public void tableChanged(TableModelEvent e) {
            updateView();
        }
    };

    public void setModel(TableModel model) {
        if (this.model != model) {
            if (this.model != null) {
                this.model.removeTableModelListener(tableModelListener);
            }
            this.model = model;
            model.addTableModelListener(tableModelListener);
            
            fixedTable.setRowSorter(null);
            table.setRowSorter(null);
            checkedTable.setRowSorter(null);
            
            modelAdapter = new TableModelAdapter();
            fixedTable.setModel(modelAdapter);
            table.setModel(modelAdapter);
            checkedModel = new CheckedModel();
            checkedTable.setModel(checkedModel);
            updateView();
        }
    }

    public void setModel(TableModel model, int fixedColumnCount) {
        fixedCol = fixedColumnCount;
        setModel(model);
    }

    public void setModel(TableModel model, int fixedColumnCount, boolean showChecked) {
        this.fixedCol = fixedColumnCount;
        this.showChecked = showChecked;
        setModel(model);
    }

    public TableModel getModel() {
        return model;
    }

    public int getFixedColumnCount() {
        return fixedCol;
    }

    public void setFixedColumnCount(int count) {
        fixedCol = count;
        modelAdapter.fireTableStructureChanged();
        updateView();
    }

    public void setShowChecked(boolean b) {
        showChecked = b;
        checkedTable.setVisible(b);
        checkedModel.fireTableStructureChanged();
        updateView();
    }
    
    public boolean isShowChecked() {
        return showChecked;
    }

    private void updateView() {
        int prefWidth = 0;
        if (showChecked) {
            TableColumn tc = checkedTable.getColumnModel().getColumn(0);
            tc.setMaxWidth(24);
            tc.setPreferredWidth(24);
            tc.setWidth(24);
            tc.setMinWidth(24);
            checkedTable.getTableHeader().setResizingColumn(tc);
            prefWidth = 24;
            checkedTable.setOpaque(false);
        }
        int rowCount = fixedTable.getRowCount();
        int colCount = fixedTable.getColumnCount();
        int index = fixedCol;
        for (int i = 0; i < index; i++) {
            TableColumn tc = fixedTable.getColumnModel().getColumn(i);
            TableCellRenderer hRenderer = fixedTable.getTableHeader().getDefaultRenderer();
            Component c = hRenderer.getTableCellRendererComponent(
                    fixedTable, tc.getIdentifier(), false, false, -1, i);
            int width = (int)c.getPreferredSize().getWidth();
            for (int row = 0; row < rowCount; row++) {
                c = fixedTable.getCellRenderer(row, i).getTableCellRendererComponent(
                        fixedTable, fixedTable.getValueAt(row, i), false, false, row, i);
                int w = (int) c.getPreferredSize().getWidth();
                width = Math.max(width, w);
            }
            fixedTable.getTableHeader().setResizingColumn(tc); // 此行很重要
            width += (fixedTable.getIntercellSpacing().width);
            tc.setWidth(width);
            prefWidth += width;
        }
        for (; index < colCount; index++) {
            TableColumn tc = fixedTable.getColumnModel().getColumn(index);
            tc.setMaxWidth(0);
            tc.setPreferredWidth(0);
            tc.setWidth(0);
            tc.setMinWidth(0);
            fixedTable.getTableHeader().getColumnModel().getColumn(index).setMaxWidth(0);
            fixedTable.getTableHeader().getColumnModel().getColumn(index).setMinWidth(0);
        }
        index = fixedCol - 1;
        for (; index >= 0; index--) {
            TableColumn tc = table.getColumnModel().getColumn(index);
            tc.setMaxWidth(0);
            tc.setPreferredWidth(0);
            tc.setWidth(0);
            tc.setMinWidth(0);
            table.getTableHeader().getColumnModel().getColumn(index).setMaxWidth(0);
            table.getTableHeader().getColumnModel().getColumn(index).setMinWidth(0);
        }
        Dimension size = new Dimension(prefWidth, fixedTable.getPreferredSize().height);
        viewport.setPreferredSize(size);
        viewport.validate();
    }

    /**
     * 获取被勾选的行数。如果勾选列不可见则返回 0。
     *
     * @return 返回被勾选的行数，如果勾选列不可见则返回 0。
     */
    public int getCheckedRowCount() {
        if (!showChecked) {
            return 0;
        }
        int checkedRowCount = 0;
        int i = checkedModel.getRowCount() - 1;
        while (i >= 0) {
            Boolean b = (Boolean) checkedModel.getValueAt(i, 0);
            if (b != null && b) {
                checkedRowCount++;
            }
            i--;
        }
        return checkedRowCount;
    }

    /**
     * 获取所有被勾选的行索引。
     *
     * @return 包含所有被勾选行索引的整数数组；如果没有被勾选的行，则返回一个空数组。
     */
    public int[] getCheckedRows() {
        if (!showChecked) {
            return new int[0];
        }
        int rowCount = checkedTable.getRowCount();
        int checkedRowCount = getCheckedRowCount();
        int[] rows = new int[checkedRowCount];
        for (int i = 0, n = 0; i < rowCount; i++) {
            Boolean b = (Boolean) checkedTable.getValueAt(i, 0);
            if (b != null && b) {
                rows[n++] = i;
            }
        }
        return rows;
    }

    /**
     * 取消勾选所有已勾选的行。
     */
    public void clearChecked() {
        checkedModel.clearChecked();
    }

    public void setCheckedRow(int row) {
        if (!showChecked) {
            throw new IllegalStateException("showChecked must be true.");
        }
        checkedTable.setValueAt(true, row, 0);
    }

    /**
     * 获取可以显示的行数
     *
     * @return 返回可以显示的行数
     */
    public int getRowCount() {
        return table.getRowCount();
    }

    /**
     * 返回列模型中的列数。注意，这可能与表模型中的列数不同。
     *
     * @return 返回列模型中的列数。
     */
    public int getColumnCount() {
        return table.getColumnCount();
    }
    
    public void setRowHeight(int rowHeight) {
        checkedTable.setRowHeight(rowHeight);
        fixedTable.setRowHeight(rowHeight);
        table.setRowHeight(rowHeight);
    }
    
    public int getRowHeight() {
        return table.getRowHeight();
    }

    /**
     * 将基于视图的行索引映射到底层 TableModel。
     *
     * @param viewRowIndex 视图中的行索引
     * @return 返回模型中的对应行索引。如果模型的内容没有进行排序，则模型和视图的索引相同。
     */
    public int convertRowIndexToModel(int viewRowIndex) {
        return table.convertRowIndexToModel(viewRowIndex);
    }

    /**
     * 将视图中位于 viewColumnIndex 的列索引映射到表模型中的列索引。
     *
     * @param viewColumnIndex 视图中的列索引
     * @return 返回模型中的对应列索引。如果 viewColumnIndex 小于 0，则返回 viewColumnIndex。
     */
    public int convertColumnIndexToModel(int viewColumnIndex) {
        return table.convertColumnIndexToModel(viewColumnIndex);
    }

    /**
     * 返回 row 和 column 位置的单元格值。
     *
     * @param rowIndex 视图中的行索引
     * @param columnIndex 视图中的列索引
     * @return 返回 row 和 column 位置的单元格值。
     */
    public Object getValueAt(int rowIndex, int columnIndex) {
        return table.getValueAt(rowIndex, columnIndex);
    }

    /**
     * 返回出现在视图中 column 列位置处的列名称。
     *
     * @param columnIndex 视图中的列索引
     * @return 返回出现在视图中 column 列位置处的列名称。
     */
    public String getColumnName(int columnIndex) {
        return table.getColumnName(columnIndex);
    }

    /**
     * 将表的选择模式设置为只允许单个选择、单个连续间隔选择或多间隔选择。
     *
     * @param selectionMode
     * @see JList#setSelectionMode
     * @beaninfo description: The selection mode used by the row and column
     * selection models. enum: SINGLE_SELECTION
     * ListSelectionModel.SINGLE_SELECTION SINGLE_INTERVAL_SELECTION
     * ListSelectionModel.SINGLE_INTERVAL_SELECTION MULTIPLE_INTERVAL_SELECTION
     * ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
     */
    public void setSelectionMode(int selectionMode) {
        fixedTable.setSelectionMode(selectionMode);
        table.setSelectionMode(selectionMode);
        checkedTable.setSelectionMode(selectionMode);
    }
    
    /**
     * 返回用来维持行选择状态的 ListSelectionModel。
     *
     * @return 返回用来维持行选择状态的 ListSelectionModel。
     */
    public ListSelectionModel getSelectionModel() {
        return table.getSelectionModel();
    }

    /**
     * 将此表的行选择模型设置为 newModel，并向其注册以获取来自新数据模型的侦听器通知。
     *
     * @param newModel 新的选择模型
     */
    public void setSelectionModel(ListSelectionModel newModel) {
        fixedTable.setSelectionModel(newModel);
        table.setSelectionModel(newModel);
        checkedTable.setSelectionModel(newModel);
    }

    /**
     * 返回选定行数。
     *
     * @return 返回选定行数。
     */
    public int getSelectedRowCount() {
        return table.getSelectedRowCount();
    }

    /**
     * 返回第一个选定行的索引；如果没有选定的行，则返回 -1。
     *
     * @return 返回第一个选定行的索引；如果没有选定的行，则返回 -1。
     */
    public int getSelectedRow() {
        return table.getSelectedRow();
    }

    public void setSelectedRow(int row) {
        table.setRowSelectionInterval(row, row);
    }

    /**
     * 返回所有选定行的索引。
     *
     * @return 包含所有选定行索引的整数数组；如果没有选定的行，则返回一个空数组
     */
    public int[] getSelectedRows() {
        return table.getSelectedRows();
    }

    public void clearSelection() {
        table.clearSelection();
    }

    public TableCellRenderer getDefaultRenderer(Class<?> columnClass) {
        return table.getDefaultRenderer(columnClass);
    }

    public void setDefaultRenderer(Class<?> columnClass, TableCellRenderer renderer) {
        fixedTable.setDefaultRenderer(columnClass, renderer);
        table.setDefaultRenderer(columnClass, renderer);
    }

    public TableCellEditor getDefaultEditor(Class<?> columnClass) {
        return table.getDefaultEditor(columnClass);
    }

    public void setDefaultEditor(Class<?> columnClass, TableCellEditor editor) {
        fixedTable.setDefaultEditor(columnClass, editor);
        table.setDefaultEditor(columnClass, editor);
    }

    public void setRowSorter(RowSorter<? extends TableModel> sorter) {
        fixedTable.setRowSorter(sorter);
        table.setRowSorter(sorter);
        checkedTable.setRowSorter(sorter);
    }

    public RowSorter<? extends TableModel> getRowSorter() {
        return table.getRowSorter();
    }

    JTable getDataTable() {
        return table;
    }

    JTable getFixedTable() {
        return fixedTable;
    }
    
    JTable getCheckedTable() {
        return checkedTable;
    }
    
    public boolean isEditable() {
        return editable;
    }

    public void setEditable(boolean editable) {
        this.editable = editable;
    }
   
    private class CheckedModel extends AbstractTableModel {
        private static final long serialVersionUID = 1L;
        
        private final HashMap<Integer, Boolean> checkedMap;

        CheckedModel() {
            checkedMap = new HashMap<Integer, Boolean>();
        }

        void clearChecked() {
            checkedMap.clear();
            fireTableDataChanged();
        }

        @Override
        public int getRowCount() {
            return model == null ? 0 : model.getRowCount();
        }

        @Override
        public int getColumnCount() {
            return showChecked ? 1 : 0;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return checkedMap.get(rowIndex);
        }

        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            checkedMap.put(rowIndex, (Boolean) aValue);
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return true;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return Boolean.class;
        }

        @Override
        public String getColumnName(int column) {
            return " ";
        }
    }
    
    private class TableModelAdapter extends AbstractTableModel {
        private static final long serialVersionUID = 1L;
        
        @Override
        public int getRowCount() {
            return model.getRowCount();
        }

        @Override
        public int getColumnCount() {
            return model.getColumnCount();
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return model.getValueAt(rowIndex, columnIndex);
        }

        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            model.setValueAt(aValue, rowIndex, columnIndex);
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return editable && model.isCellEditable(rowIndex, columnIndex);
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return model.getColumnClass(columnIndex);
        }

        @Override
        public String getColumnName(int column) {
            return model.getColumnName(column);
        }
    }
    
}
